#include <drivers.h>
#include <drivers/serial.h>
#include <drivers/pin.h>

#include <stm32f10x.h>

struct stm32_usart_config {
	const char *name;
	USART_TypeDef *usart;
	IRQn_Type irq_type;
};

struct stm32_usart {
	struct serial serial;
	struct stm32_usart_config *config;
};

enum {
#ifdef USART1_ENABLE
	USART1_INDEX,
#endif
#ifdef USART2_ENABLE
	USART2_INDEX,
#endif
#ifdef USART3_ENABLE
	USART3_INDEX,
#endif
};

static struct stm32_usart_config usart_config[] = {
#ifdef USART1_ENABLE
	{
		.name = USART1_NAME,
		.usart = USART1,
		.irq_type = USART1_IRQn,
	},
#endif
#ifdef USART2_ENABLE
	{
		.name = USART2_NAME,
		.usart = USART2,
		.irq_type = USART2_IRQn,
	},
#endif
#ifdef USART3_ENABLE
	{
		.name = USART3_NAME,
		.usart = USART3,
		.irq_type = USART3_IRQn,
	},
#endif
};

static struct stm32_usart usart[sizeof(usart_config) / sizeof(usart_config[0])];

static err_t serial_configure(struct serial *serial, struct serial_configure *configure)
{
	USART_InitTypeDef usart_init_struct;
	base_t tx_pin, rx_pin;
	struct stm32_usart *usart = container_of(serial, struct stm32_usart, serial);

	if (usart->config->usart == USART1) {
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

		tx_pin = hw_pin_get("PA9");
		rx_pin = hw_pin_get("PA10");

		hw_pin_mode(tx_pin, PIN_MODE_AF_PP);
		hw_pin_mode(rx_pin, PIN_MODE_INPUT);
	} else if (usart->config->usart == USART2) {
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

		tx_pin = hw_pin_get("PA2");
		rx_pin = hw_pin_get("PA3");

		hw_pin_mode(tx_pin, PIN_MODE_AF_PP);
		hw_pin_mode(rx_pin, PIN_MODE_INPUT);
	} else if (usart->config->usart == USART3) {
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

		tx_pin = hw_pin_get("PB10");
		rx_pin = hw_pin_get("PB11");

		hw_pin_mode(tx_pin, PIN_MODE_AF_PP);
		hw_pin_mode(rx_pin, PIN_MODE_INPUT);
	} else {
		return -1;
	}

	switch (usart->serial.configure->flowcontrol) {
	case FLOWCONTROL_NONE:
		usart_init_struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
		break;
	case FLOWCONTROL_RTSCTS:
		usart_init_struct.USART_HardwareFlowControl = USART_HardwareFlowControl_RTS_CTS;
		break;
	default:
		usart_init_struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
		break;
	}

	switch (usart->serial.configure->parity) {
	case PARITY_NONE:
		usart_init_struct.USART_Parity = USART_Parity_No;
		break;
	case PARITY_EVEN:
		usart_init_struct.USART_Parity = USART_Parity_Even;
		break;
	case PARITY_ODD:
		usart_init_struct.USART_Parity = USART_Parity_Odd;
		break;
	default:
		usart_init_struct.USART_Parity = USART_Parity_No;
		break;
	}

	switch (usart->serial.configure->stop_bits) {
	case STOP_BITS_1:
		usart_init_struct.USART_StopBits = USART_StopBits_1;
		break;
	case STOP_BITS_2:
		usart_init_struct.USART_StopBits = USART_StopBits_2;
		break;
	default:
		usart_init_struct.USART_StopBits = USART_StopBits_1;
		break;
	}

	switch (usart->serial.configure->data_bits) {
	case DATA_BITS_8:
		usart_init_struct.USART_WordLength = USART_WordLength_8b;
		break;
	case DATA_BITS_9:
		usart_init_struct.USART_WordLength = USART_WordLength_9b;
		break;
	default:
		usart_init_struct.USART_WordLength = USART_WordLength_8b;
		break;
	}

	usart_init_struct.USART_BaudRate = serial->configure->baud_rate;
	usart_init_struct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

	USART_Init(usart->config->usart, &usart_init_struct);
	USART_Cmd(usart->config->usart, ENABLE);
	return 0;
}

static err_t serial_control(struct serial *serial, int cmd, void *arg)
{
	switch (cmd) {

	};
	return 0;
}

static int serial_putc(struct serial *serial, char c)
{
	struct stm32_usart *usart = container_of(serial, struct stm32_usart, serial);

	USART_SendData(usart->config->usart, c);

	while (USART_GetFlagStatus(usart->config->usart, USART_FLAG_TXE) == 0);

	return 0;
}

static int serial_getc(struct serial *serial)
{
	int ret = 0;
	struct stm32_usart *usart = container_of(serial, struct stm32_usart, serial);

	while (USART_GetFlagStatus(usart->config->usart, USART_FLAG_RXNE) == 0);

	ret = USART_ReceiveData(usart->config->usart);

	return ret;
}

static size_t serial_transmit(struct serial *serial, uint8_t *buf, size_t size, int direction)
{
	return 0;
}

static struct serial_configure configure = {
	.baud_rate = BAUD_RATE_115200,
	.data_bits = DATA_BITS_8,
	.stop_bits = STOP_BITS_1,
	.flowcontrol = FLOWCONTROL_NONE,
	.parity = PARITY_NONE,
};

static struct serial_ops serial_ops = {
	.configure = serial_configure,
	.control = serial_control,
	.putc = serial_putc,
	.getc = serial_getc,
	.transmit = serial_transmit
};

static void usart_isr(struct stm32_usart *usart)
{

}

void usart_device_init(void)
{
	int ret = 0;
	int usart_sum = sizeof(usart) / sizeof(usart[0]);

	for(int i = 0; i < usart_sum; ++i) {
		usart[i].serial.ops = &serial_ops;
		usart[i].serial.configure = &configure;
		usart[i].serial.oflag = 1;
		usart[i].config = &usart_config[i];

		ret = hw_serial_register(&usart[i].serial, usart[i].config->name, NULL);

		assert(ret >= 0);
	}
}
INIT_DEVICE_EXPORT(usart_device_init);

#ifdef USART1_ENABLE
void USART1_IRQHandler(void)
{
	usart_isr(&usart[USART1_INDEX]);
}
#endif
#ifdef USART2_ENABLE
void USART2_IRQHandler(void)
{
	usart_isr(&usart[USART2_INDEX]);
}
#endif
#ifdef USART3_ENABLE
void USART3_IRQHandler(void)
{
	usart_isr(&usart[USART3_INDEX]);
}
#endif
